package com.tianji.learning.utils;

import com.tianji.common.utils.JsonUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.*;

/**
 * 实现了一个基于 Redis 缓存 + Java 延迟队列的机制，用来延迟判断用户是否停止学习，并在合适的时间将学习记录持久化。
 */
@Slf4j
@Component //  将当前类声明为 Spring Bean
@RequiredArgsConstructor
public class LearningRecordDelayTaskHandler {

    //用于操作 Redis 缓存。
    private final StringRedisTemplate redisTemplate;
    //Java 的延迟队列，存放即将被执行的任务。
    private final DelayQueue<DelayTask<RecordTaskData>> queue = new DelayQueue<>();
    //Redis 中存储学习记录的 key 模板
    private final static String RECORD_KEY_TEMPLATE = "learning:record:{}";
    //MyBatis 的数据库访问接口，持久化学习记录。
    private final LearningRecordMapper recordMapper;
    //课程服务接口，用于更新课表信息。
    private final ILearningLessonService lessonService;
    //控制延迟处理线程的运行状态。
    private static volatile boolean begin = true;

    /**
     * 创建多线程池
     */
    static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
            16,
            20,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(10)
    );


    /**
     * 初始化后异步启动任务处理线程。
     * 项目启动后  当前类实例化  属性注入之后  方法就会运行
     */
    @PostConstruct
    public void init() {
        log.info("初始化执行学习记录处理的延迟任务~~~");
        CompletableFuture.runAsync(this::handleDelayTask);//开启新线程执行handleDelayTask
//        log.info("初始化执行学习记录处理的延迟任务结束~~~");
    }

    /**
     * Bean 销毁前关闭任务处理线程。
     */
    @PreDestroy
    public void destroy() {
        log.debug("关闭学习记录处理的延迟任务");
        begin = false;
    }

    /**
     * 核心处理逻辑：handleDelayTask
     * <p>
     * 上面两个方法是为了这个方法而服务
     */
    private void handleDelayTask() {
        while (begin) {
            try {
                // 1.尝试获取任务
                DelayTask<RecordTaskData> task = queue.take();//queue.take() 会阻塞直到有任务到期。
                poolExecutor.submit(new Runnable() {
                    @Override
                    public void run() {
//                log.debug("获取到要处理的播放记录任务");
                        RecordTaskData data = task.getData();

                        // 2.读取Redis缓存
                        LearningRecord record = readRecordCache(data.getLessonId(), data.getSectionId());
                        log.info("正在处理播放记录任务：{} 缓存中的数据: {}", data, record);
                        if (record == null) {
//                            continue;//从 Redis 中读取缓存记录，若无则跳过。
                            return;
                        }
//                        // 3.比较数据
                        if (!Objects.equals(data.getMoment(), record.getMoment())) {
                            // 4.如果不一致，播放进度在变化，无需持久化
//                            continue;//如果播放位置 moment 变化了，说明用户仍在观看，跳过。
                            return;
                        }
                        // 5.如果一致，证明用户离开了视频，需要持久化
                        // 5.1.更新学习记录
                        record.setFinished(null);
                        recordMapper.updateById(record);
                        // 5.2.更新课表
                        LearningLesson lesson = new LearningLesson();
                        lesson.setId(data.getLessonId());
                        lesson.setLatestSectionId(data.getSectionId());
                        lesson.setLatestLearnTime(LocalDateTime.now());
                        lessonService.updateById(lesson); //播放位置未变，说明用户离开了，将记录写入数据库并更新课程最近学习状态。
                    }
                });


                log.debug("准备持久化学习记录信息");
            } catch (Exception e) {
                log.error("处理播放记录任务发生异常", e);
            }
        }
    }

    /**
     * 添加延迟任务
     * <p>
     * 写入 Redis 缓存。
     * 添加一个延迟 20 秒的任务。
     *
     * @param record
     */
    public void addLearningRecordTask(LearningRecord record) {
        // 1.添加数据到Redis缓存
        writeRecordCache(record);
        // 2.提交延迟任务到延迟队列 DelayQueue
        queue.add(new DelayTask<>(new RecordTaskData(record), Duration.ofSeconds(20)));
    }

    /**
     * 写入 Redis 缓存。
     * 将学习记录转换为 JSON 存入 Redis。
     * 设置 Redis 缓存过期时间为 1 分钟。
     *
     * @param record
     */
    public void writeRecordCache(LearningRecord record) {
        log.debug("更新学习记录的缓存数据");
        try {
            // 1.数据转换
            String json = JsonUtils.toJsonStr(new RecordCacheData(record));
            // 2.写入Redis
            String key = StringUtils.format(RECORD_KEY_TEMPLATE, record.getLessonId());
            redisTemplate.opsForHash().put(key, record.getSectionId().toString(), json);
            // 3.添加缓存过期时间
            redisTemplate.expire(key, Duration.ofMinutes(1));
        } catch (Exception e) {
            log.error("更新学习记录缓存异常", e);
        }
    }

    /**
     * 从缓存中读取学习记录
     * 从 Redis 读取缓存.
     * 读取并反序列化 Redis 缓存中的数据。
     *
     * @param lessonId
     * @param sectionId
     * @return
     */
    public LearningRecord readRecordCache(Long lessonId, Long sectionId) {
        try {
            // 1.读取Redis数据
            String key = StringUtils.format(RECORD_KEY_TEMPLATE, lessonId);
            Object cacheData = redisTemplate.opsForHash().get(key, sectionId.toString());
            if (cacheData == null) {
                return null;
            }
            // 2.数据检查和转换
            return JsonUtils.toBean(cacheData.toString(), LearningRecord.class);
        } catch (Exception e) {
            log.error("缓存读取异常", e);
            return null;
        }
    }

    /**
     * 删除缓存中  指定lessonId 下的小节Id
     * 从 Redis 中移除某个章节的学习记录。
     *
     * @param lessonId
     * @param sectionId
     */
    public void cleanRecordCache(Long lessonId, Long sectionId) {
        // 删除数据
        String key = StringUtils.format(RECORD_KEY_TEMPLATE, lessonId);
        redisTemplate.opsForHash().delete(key, sectionId.toString());
    }

    /**
     * 内部类
     */
    //用于 Redis 缓存存储的简化版本数据结构。
    @Data
    @NoArgsConstructor
    private static class RecordCacheData {
        private Long id;
        private Integer moment;
        private Boolean finished;

        public RecordCacheData(LearningRecord record) {
            this.id = record.getId();
            this.moment = record.getMoment();
            this.finished = record.getFinished();
        }
    }

    //用于延迟任务中的任务数据结构。
    @Data
    @NoArgsConstructor
    private static class RecordTaskData {
        private Long lessonId;
        private Long sectionId;
        private Integer moment;

        public RecordTaskData(LearningRecord record) {
            this.lessonId = record.getLessonId();
            this.sectionId = record.getSectionId();
            this.moment = record.getMoment();
        }
    }
}
